import java.util.ArrayList;
import java.lang.Math;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author jesus, nate, mike
 */
public class Modifiers {
    
    /* invert pressure values */
    public static ArrayList<Point> invertPressures(Stroke s) {
        ArrayList<Point> inverted = new ArrayList<Point>();
        for(int i = 0; i < s.dataPoints.size(); ++i) {
            Point p = s.dataPoints.get(i);
            p.pressure = (float) 1.0 - p.pressure;
            inverted.add(p);
        }
        return inverted;
    }
    
    /* bring pressure values closer to a given target by a given multiplier (<1)*/
    public static ArrayList<Point> unifyPressures(Stroke s,
                                                  double target,
                                                  double amount) {
        ArrayList<Point> unified = new ArrayList<Point>();
        if(amount > 1)
            return unified;
        for(int i = 0; i < s.dataPoints.size(); ++i) {
            Point p = s.dataPoints.get(i);
            p.pressure += (target - p.pressure) * amount;
            p.pressure = Math.max(p.pressure, 0);
            p.pressure = Math.min(p.pressure, 1);
            unified.add(p);
        }
        return unified;
    }
    
    /* move pressure values further from a given target by a given multiplier (<1)*/
    public static ArrayList<Point> divergePressures(Stroke s,
                                                  double target,
                                                  double amount) {
        ArrayList<Point> diverged = new ArrayList<Point>();
        if(amount > 1)
            return diverged;
        for(int i = 0; i < s.dataPoints.size(); ++i) {
            Point p = s.dataPoints.get(i);
            p.pressure -= (target - p.pressure) * amount;
            p.pressure = Math.max(p.pressure, 0);
            p.pressure = Math.min(p.pressure, 1);
            diverged.add(p);
        }
        return diverged;
    }

    /* increase the curvature of a stroke by a given factor */
    public static ArrayList<Point> tightenCurvature(Stroke s,
                                                    ArrayList<Integer> changes,
                                                    double maxFactor) {
        ArrayList<Point> tightened = new ArrayList<Point>();
        if(changes.size() < 1)
            return s.dataPoints;
        if(maxFactor > 0.5)
            maxFactor = 0.5;
        int startIndex = 0;
        for(int i = 0; i < changes.size(); ++i) {
            int endIndex = changes.get(i);
            double xMin = 0, xMax = 0, yMin = 0, yMax = 0;
            double x = 0, y = 0;
            for(int j = startIndex; j < endIndex; ++j) {
                x = s.dataPoints.get(j).x_coor;
                y = s.dataPoints.get(j).y_coor;
                if (j == startIndex) {
                    xMin = xMax = x;
                    yMin = yMax = y;
                }
                if(x < xMin) xMin = x;
                if(x > xMax) xMax = x;
                if(y < yMin) yMin = y;
                if(y > yMax) yMax = y;
            }
            double xMed = (xMax + xMin) / 2.0;
            double yMed = (yMax + yMin) / 2.0;
            /*System.out.println("xmin: " + xMin + ", xmax: " + xMax +
                               ", ymin: " + yMin + ", ymax: " + yMax);
            System.out.println("xMed: " + xMed + ", yMed: " + yMed);*/
            int midIndex = (startIndex + endIndex) / 2;
            double slope = maxFactor / midIndex;
            for(int j = startIndex; j < endIndex; ++j) {
                Point p = s.dataPoints.get(j);
                double xDiff = xMed - p.x_coor;
                double yDiff = yMed - p.y_coor;
                //System.out.println("p.x_coor: " + p.x_coor + ", p.y_coor: " + p.y_coor);
                //System.out.println("xDiff: " + xDiff + ", yDiff: " + yDiff);
                double factor = (j < midIndex)?
                    slope * (j - startIndex):
                    slope * (endIndex - j);
                p.x_coor += (xDiff * factor);
                p.y_coor += (yDiff * factor);
                tightened.add(p);
            }
            startIndex = endIndex;
        }
        return tightened;
    }

    /* decrease the curvature of a stroke by a given factor */
    public static ArrayList<Point> slackenCurvature(Stroke s,
                                                    ArrayList<Integer> changes,
                                                    double maxFactor) {
        ArrayList<Point> slackened = new ArrayList<Point>();
        if(changes.size() < 1)
            return s.dataPoints;
        if(maxFactor > 1.5)
            maxFactor = 1.5;
        int startIndex = 0;
        for(int i = 0; i < changes.size(); ++i) {
            int endIndex = changes.get(i);
            double xMin = 0, xMax = 0, yMin = 0, yMax = 0;
            double x = 0, y = 0;
            for(int j = startIndex; j < endIndex; ++j) {
                x = s.dataPoints.get(j).x_coor;
                y = s.dataPoints.get(j).y_coor;
                if (j == startIndex) {
                    xMin = xMax = x;
                    yMin = yMax = y;
                }
                if(x < xMin) xMin = x;
                if(x > xMax) xMax = x;
                if(y < yMin) yMin = y;
                if(y > yMax) yMax = y;
            }
            double xMed = (xMax + xMin) / 2.0;
            double yMed = (yMax + yMin) / 2.0;
            /*System.out.println("xmin: " + xMin + ", xmax: " + xMax +
                               ", ymin: " + yMin + ", ymax: " + yMax);
            System.out.println("xMed: " + xMed + ", yMed: " + yMed);*/
            int midIndex = (startIndex + endIndex) / 2;
            double slope = maxFactor / midIndex;
            for(int j = startIndex; j < endIndex; ++j) {
                Point p = s.dataPoints.get(j);
                double xDiff = xMed - p.x_coor;
                double yDiff = yMed - p.y_coor;
                //System.out.println("p.x_coor: " + p.x_coor + ", p.y_coor: " + p.y_coor);
                //System.out.println("xDiff: " + xDiff + ", yDiff: " + yDiff);
                double factor = (j < midIndex)?
                    slope * (j - startIndex):
                    slope * (endIndex - j);
                p.x_coor -= (xDiff * factor);
                p.y_coor -= (yDiff * factor);
                slackened.add(p);
            }
            startIndex = endIndex;
        }
        return slackened;
    }
     
    /* smooth out the stroke by a given multiplier (0<amount<1)*/
    public static ArrayList<Point> smoothCurvature(Stroke s, double amount) {
        ArrayList<Point> smooth = new ArrayList<Point>();
        int size = s.dataPoints.size();
        double x1, x2, y1, y2; //points of segment
        double xc, yc;  //points to move
        double xj, yj;  //points on segment line for reference
        double xn, yn;  //new points
        double xdist, ydist;
        double xjdist, yjdist;
        int sample=20; //Points to Modify
        smooth= s.dataPoints;
        for(int i=0; i<size-sample; i+=10)
        {
            x1= s.dataPoints.get(i).x_coor;
            y1= s.dataPoints.get(i).y_coor;
            x2= s.dataPoints.get(i+sample).x_coor;
            y2= s.dataPoints.get(i+sample).y_coor;
            if((x2-x1)!=0)
            {    
                xdist=(x2-x1)/sample;
                ydist=(y2-y1)/sample;
                for(int j=0; j<sample-1; j++)
                {
                    xc= s.dataPoints.get(i+j).x_coor;
                    yc= s.dataPoints.get(i+j).y_coor;
                    xn=xc;
                    yn=yc;
                    xj=x1+(j*xdist);
                    yj=y1+(j*ydist);
                    xjdist=Math.abs(xj-xc);
                    yjdist=Math.abs(yj-yc);
                    if(xc<xj)
                    {
                        xn= xc+(xjdist*amount);
                    }
                    if(xc>xj)
                    {
                        xn= xc-(xjdist*amount);
                    }
                    if(yc<yj)
                    {
                        yn= yc+(yjdist*amount);
                    }
                    if(yc>yj)
                    {
                        yn= yc-(yjdist*amount);
                    }
                    smooth.get(i+j).x_coor=xn;
                    smooth.get(i+j).y_coor=yn;
                }
            }
        }        
        return smooth;
    }

    /* increase the jitter of a stroke by a given factor */
    public static ArrayList<Point> increaseJitter(Stroke s,
                                                  double factor,
                                                  int rate) {
        ArrayList<Point> jittered = new ArrayList<Point>();
        for(int i = 0; i < s.dataPoints.size(); i++) {
            Point p = s.dataPoints.get(i);
            if((i % rate) ==0) {
                p.x_coor += Math.random() * factor;
                p.y_coor += Math.random() * factor;
            }
            jittered.add(p);
        }
        return jittered;
    }

    /* smooth stroke by removing a rate of the point samples */
    public static ArrayList<Point> smoothCurvature2(Stroke s,
                                                int rate) {
        ArrayList<Point> smoothed = new ArrayList<Point>();
        for(int i = 0; i < s.dataPoints.size(); ++i) {
            if((i % rate) == 0)
                smoothed.add(s.dataPoints.get(i));
        }
        return smoothed;
    }

    /* bring tilt values closer to a given target by a given multiplier (<1)*/
    public static ArrayList<Point> unifyTilt(Stroke s,
                                                  double target,
                                                  double amount) {
        ArrayList<Point> unified = new ArrayList<Point>();
        if(amount > 1)
            return unified;
        for(int i = 0; i < s.dataPoints.size(); ++i) {
            Point p = s.dataPoints.get(i);
            p.tilt_x += (target - p.tilt_x) * amount;
            p.tilt_x = Math.max(p.tilt_x, 0);
            p.tilt_x = Math.min(p.tilt_x, 1);
            p.tilt_y += (target - p.tilt_y) * amount;
            p.tilt_y = Math.max(p.tilt_y, 0);
            p.tilt_y = Math.min(p.tilt_y, 1);
            unified.add(p);
        }
        return unified;
    }

    /* move tilt values further from a given target by a given multiplier (<1)*/
    public static ArrayList<Point> divergeTilt(Stroke s,
                                                  double target,
                                                  double amount) {
        ArrayList<Point> diverged = new ArrayList<Point>();
        if(amount > 1)
            return diverged;
        for(int i = 0; i < s.dataPoints.size(); ++i) {
            Point p = s.dataPoints.get(i);
            p.tilt_x -= (target - p.tilt_x) * amount;
            p.tilt_x = Math.max(p.tilt_x, 0);
            p.tilt_x = Math.min(p.tilt_x, 1);
            p.tilt_y -= (target - p.tilt_y) * amount;
            p.tilt_y = Math.max(p.tilt_y, 0);
            p.tilt_y = Math.min(p.tilt_y, 1);
            diverged.add(p);
        }
        return diverged;
    }

    /* invert tilt values */
    public static ArrayList<Point> invertTilt(Stroke s) {
        ArrayList<Point> inverted = new ArrayList<Point>();
        for(int i = 0; i < s.dataPoints.size(); ++i) {
            Point p = s.dataPoints.get(i);
            p.tilt_x = (float)  -1* p.tilt_x;
            p.tilt_y = (float)  -1* p.tilt_y;
            inverted.add(p);
        }
        return inverted;
    }

    /* scale stroke in X direction */
    public static ArrayList<Point> scaleStrokeX(Stroke s, double factor) {
        ArrayList<Point> shortened = new ArrayList<Point>();
        Point centroid = Features.centroid(s);
        for(int i = 0; i < s.dataPoints.size(); ++i) {
            Point p = s.dataPoints.get(i);
            double xDiff = p.x_coor - centroid.x_coor;
            p.x_coor += xDiff * factor;
            shortened.add(p);
             }
        return shortened;
    }
    /* Flip  Points*/
    public static ArrayList<Point> flipPoints(Stroke s, double theta) {
        ArrayList<Point> inverted = new ArrayList<Point>();
        double x, y;
        double Tx=0;
        double Ty=0;
        //get center point, set Tx, Ty
        Point T = Features.centroid(s);
        for(int i = 0; i < s.dataPoints.size(); i++) {
            Point p = s.dataPoints.get(i);
            x = s.dataPoints.get(i).x_coor;
            y = s.dataPoints.get(i).y_coor;
            p.x_coor = (x*Math.cos(theta))-(y*Math.sin(theta))+T.x_coor;
            p.y_coor = (x*Math.sin(theta))+(y*Math.cos(theta))+T.y_coor;
            //System.out.println(p.x_coor+"  "+p.y_coor);
            inverted.add(p);
        }
        return inverted;
    }

    /* scale stroke in Y direction*/
    public static ArrayList<Point> scaleStrokeY(Stroke s, double factor) {
        ArrayList<Point> shortened = new ArrayList<Point>();
        Point centroid = Features.centroid(s);
        for(int i = 0; i < s.dataPoints.size(); ++i) {
            Point p = s.dataPoints.get(i);
            double yDiff = p.y_coor - centroid.y_coor;
            p.y_coor += yDiff * factor;
            shortened.add(p);
        }
        return shortened;
    }

    /* move the stroke closer to or further from the center of the screen */
    public static ArrayList<Point> translateByCenter(Stroke s, double factor) {
        ArrayList<Point> trans = new ArrayList<Point>();
        for(int i = 0; i < s.dataPoints.size(); ++i) {
            Point p = s.dataPoints.get(i);
            double xDiff = p.x_coor - 750;
            double yDiff = p.y_coor - 400;
            p.x_coor += xDiff * factor;
            p.y_coor += yDiff * factor;
            trans.add(p);
        }
        return trans;
    }
}
